package org.wxd.bi.api;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wxd.ann.ShowIndex;
import org.wxd.batis.EntityField;
import org.wxd.batis.sql.SqlEntityTable;
import org.wxd.batis.sql.mysql.MysqlDataWrapper;
import org.wxd.bi.api.po.BasePush;
import org.wxd.bi.api.po.pushlog.LoginRoleLog;
import org.wxd.bi.api.po.pushlog.LoginUserLog;
import org.wxd.bi.api.struct.PushColumn;
import org.wxd.bi.api.struct.PushMapping;
import org.wxd.bi.api.struct.ann.ShowGroup;
import org.wxd.collection.SplitCollection;
import org.wxd.lang.RunResult;
import org.wxd.net.web.template.ann.HtmlStyle;
import org.wxd.str.json.Json2Object;
import org.wxd.system.AnnUtil;
import org.wxd.threading.Work;
import org.wxd.threading.Worker;

import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: Troy.Chen(無心道, 15388152619)
 * @version: 2021-10-12 14:08
 **/
public abstract class PushApi implements Serializable, Worker {

    private static final long serialVersionUID = 1L;
    private static final Logger log = LoggerFactory.getLogger(PushApi.class);

    protected boolean batch = true;
    protected int batchSize = 50;
    protected int appId;
    protected String token;
    protected Work work;
    protected Map<String, SplitCollection<String>> pushMap;

    public PushApi(int appId, String token) {
        this.appId = appId;
        this.token = token;

        this.pushMap = new ConcurrentHashMap<>();
        this.work = new Work(33, "push", this);

    }

    @Override
    public boolean isClose() {
        return PushApi.this.pushMap.isEmpty();
    }

    @Override
    public void job() throws Exception {
        Map<String, SplitCollection<String>> tmp;
        synchronized (PushApi.this) {
            if (PushApi.this.pushMap.isEmpty()) return;
            tmp = PushApi.this.pushMap;
            PushApi.this.pushMap = new ConcurrentHashMap<>();
        }
        for (Map.Entry<String, SplitCollection<String>> entry : tmp.entrySet()) {
            final SplitCollection<String> entryValue = entry.getValue();
            while (!entryValue.isEmpty()) {
                PushApi.this.pushData(entry.getKey(), entryValue.removeFrist());
            }
        }
    }

    /**
     * 把数据模型 转化成映射关系
     *
     * @param clazz
     * @return
     */
    public static PushMapping buildPushMapping(Class<?> clazz) throws Exception {
        SqlEntityTable entityTable = MysqlDataWrapper.Default.asEntityTable(clazz);
        PushMapping pushMapping = new PushMapping()
                .setTableName(entityTable.getTableName())
                .setTableComment(entityTable.getTableComment());
        ShowGroup showGroup = AnnUtil.annotation(clazz, ShowGroup.class);
        if (showGroup != null) {
            pushMapping.setShowGroup(showGroup.group());
        }
        ShowIndex showIndex = AnnUtil.annotation(clazz, ShowIndex.class);
        if (showIndex != null) {
            pushMapping.setShowIndex(showIndex.value());
        }
        LinkedHashMap<String, EntityField> columnMap = entityTable.getColumnMap();
        for (EntityField value : columnMap.values()) {
            PushColumn pushColumn;
            if (PushColumn.ID.getName().equals(value.getColumnName())) {
                pushColumn = PushColumn.ID.clone();
            } else if (PushColumn.SAVE_TIME.getName().equals(value.getColumnName())) {
                pushColumn = PushColumn.SAVE_TIME.clone();
            } else if (PushColumn.EXPAND.getName().equals(value.getColumnName())) {
                pushColumn = PushColumn.EXPAND.clone();
            } else {
                pushColumn = new PushColumn()
                        .setName(value.getColumnName())
                        .setPk(value.isColumnKey())
                        .setTypeStr(value.getFieldTypeString())
                        .setComment(value.getColumnComment())
                        .setDbIndex(value.isColumnIndex());
            }
            HtmlStyle htmlStyle = AnnUtil.annotation(value.getField(), HtmlStyle.class);
            if (htmlStyle != null) {
                pushColumn.getHtmlStyle().setValue(htmlStyle.value());
            }
            ShowIndex columnIndex = AnnUtil.annotation(value.getField(), ShowIndex.class);
            if (columnIndex != null) {
                pushColumn.setShowIndex(columnIndex.value());
            }
            pushMapping.getColumnList().add(pushColumn);
        }
        pushMapping.sortColumn();
        return pushMapping;
    }

    protected void printRunResult(String r) {
        RunResult runResult = Json2Object.parseObject(r, RunResult.class);
        if (runResult.getCode() != 0) {
            log.warn(r, new RuntimeException(runResult.getMsg()));
        } else if (log.isDebugEnabled()) {
            log.debug(r);
        }
    }

    public void pushMapping(Class<BasePush> modelClass) throws Exception {
        pushMapping(modelClass, 0);
    }

    /**
     * @param modelClass
     * @param showIndex  显示顺序，数字越小越靠前
     */
    public void pushMapping(Class<BasePush> modelClass, int showIndex) throws Exception {
        PushMapping build = buildPushMapping(modelClass);
        pushMapping(build, showIndex);
    }

    public void pushMapping(PushMapping build, int showIndex) {
        if (build.getShowIndex() == 0) {
            build.setShowIndex(showIndex);
        }
        pushMapping0(build);
    }

    protected abstract void pushMapping0(PushMapping build);

    /**
     * @param userId
     * @param userName
     * @param userCreateTime
     * @param channel
     * @param os
     * @param imei
     * @param ip             ip地址
     * @param region         登录地址，国家，洲市区县
     * @param expand         扩展信息，最多2万个字符，没有索引
     */
    public void pushLoginUser(long userId, String userName, long userCreateTime,
                              String channel,
                              String os, String imei,
                              String ip, String region,
                              String expand) {
        LoginUserLog loginLog = new LoginUserLog()
                .setUserId(userId)
                .setUserName(userName)
                .setCreateTime(userCreateTime)
                .setChannel(channel)
                .setOs(os)
                .setImei(imei)
                .setIp(ip)
                .setRegion(region)
                .setExpand(expand)
                .setSaveTime(System.currentTimeMillis());
        pushData(loginLog);
    }

    /**
     * @param userId
     * @param userName
     * @param channel
     * @param roleId
     * @param roleName
     * @param roleCreateTime
     * @param ip
     * @param region         登录地址，国家，洲市区县
     * @param expand         扩展信息，最多2万个字符，没有索引
     */
    public void pushLoginRole(long userId, String userName, String channel,
                              long roleId, String roleName, long roleCreateTime,
                              String ip, String region,
                              String expand) {
        LoginRoleLog loginLog = new LoginRoleLog()
                .setUserId(userId)
                .setUserName(userName)
                .setChannel(channel)
                .setRoleId(roleId)
                .setRoleName(roleName)
                .setCreateTime(roleCreateTime)
                .setIp(ip)
                .setRegion(region)
                .setExpand(expand)
                .setSaveTime(System.currentTimeMillis());
        pushData(loginLog);
    }

    public void pushData(BasePush dbModel) {
        pushData(dbModel.pushName(), dbModel.pushString());
    }

    /**
     * @param tableName 映射表名
     * @param data      json格式的数据结构 如 {@link BasePush#pushString()}
     */
    public void pushData(String tableName, String data) {
        Map<String, SplitCollection<String>> tmp;
        synchronized (this) {
            tmp = this.pushMap;
        }
        tmp.computeIfAbsent(tableName, l -> new SplitCollection<>(10))
                .add(data);
    }

    protected abstract void pushData(String tableName, Collection<String> datas);

    public boolean isBatch() {
        return batch;
    }

    public PushApi setBatch(boolean batch) {
        this.batch = batch;
        return this;
    }

    public int getBatchSize() {
        return batchSize;
    }

    public PushApi setBatchSize(int batchSize) {
        this.batchSize = batchSize;
        return this;
    }

    public Work getWork() {
        return work;
    }

    public Map<String, SplitCollection<String>> getPushMap() {
        return pushMap;
    }

}
